/*
* DREAMUNIT 1.0 RUNTIME API
* Copyright 2009, Alexei White
*
* http://www.dreamunit.com
*/

/**
* @author: Alexei White
* @url: http://www.dreamunit.com/
*/

var dreamunit = dreamunit || {};

/**
* @fileoverview General purpose utilities that gadgets can use.
*/

/**
* @static
* @class Provides the runtime test interface.
* @name dreamunit
*/

var dreamunit = function() {
	
	return /** @scope dreamunit */ {
		/*
		* Tests that a condition is true and outputs the results.
		* @param {Boolean} pass Indicate if the test passed.
		* @param {String} msg The message to display as the test result.
		* @param {Variant} expected (Optional) The expected value.
		* @param {Variant} actual (Optional) The actual value.
		* @member dreamunit
		*/
		ok: function( pass, msg, expected, actual ) {
			
			if (pass && pass== true) {
				if (expected && actual) {
					dreamunit.log(msg + "**DWSPLIT**" + pass + "**DWSPLIT**" + dreamunit.diff.diffString(expected, actual));
				} else {
					dreamunit.log(msg + "**DWSPLIT**true**DWSPLIT**");
				}
			} else {
				if (expected && actual) {
					dreamunit.log(msg + "**DWSPLIT**" + pass + "**DWSPLIT**" + dreamunit.diff.diffString(expected, actual));
				} else {
					dreamunit.log(msg + "**DWSPLIT**false**DWSPLIT**");
				}
			}
		},

		/*
		* Compares two objects to see if they are similar. If they are,
		* then the test passes, otherwise it fails.
		* @param {Object} needle The object that contains the properties to look for.
		* @param {Object} haystack The object to look within.
		* @param {String} msg The message to display with the test results.
		* @member dreamunit		
		*/
		containsObject: function( needle, haystack, msg ) {
			var foundit = true;
			for (item in needle) {
				if (!haystack[item] || haystack[item].toString() != needle[item].toString())
					foundit = false;
			}
			return foundit;
		},
	
		/*
		* Compares two strings to see if they are the same. If they are,
		* then the test passes, otherwise it fails.
		* @param {String} expected The expected text.
		* @param {String} actual The value to test.
		* @param {String} msg The message to display with the test results.
		* @member dreamunit		
		*/
		compare: function( expected, actual, msg ) {
			var pass = expected == actual;
			dreamunit.ok(pass, msg, expected, actual);
		},

		/*
		* Compares a string to a given RegEx. If the string matches
		* then the test passes, otherwise it fails.
		* @param {String} expected The RegEx to test the value against.
		* @param {String} actual The value to test.
		* @param {String} msg The message to display with the test results.
		* @member dreamunit		
		*/
		reCompare: function( expected, result, msg ) {
			if ( RegExp( expected ).test( result ) ) {
				return this.compare( result, result, msg );
			} else {
				return this.compare( expected, result, msg );
			}
		},
	
		/*
		* Completes the test
		* @member dreamunit
		*/
		testDone: function() {
			var container = document.getElementById("dreamunit_results");
			if (!container) {
				// Create the container if it doesn't exist.
				container = document.createElement("div");
				container.style.display = "none";
				container.id = "dreamunit_results";
				document.body.appendChild(container);
			}
			
			var results = "";
			
			// Iterate through the log and append the results
			for (var i = 0; i < this.logEntries.length; i++) {
				results += this.logEntries[i] + "**DREAMUNIT_ENTRY**";
			}
			
			// output the result
			container.innerHTML = results;
		},
	
	    //---------------------------------------------------------------------
	    // Event Simulation
	    //---------------------------------------------------------------------

	    /*
		* Simulates a click on the given node.
		* @param {HTMLElement} node The DOM node to act on.
		* @member dreamunit		
		*/
	    click: function( node ){
	      //node = this.id( node );

	      if ( node.click ) {
	        return node.click();
	      }

	      var doc = node.ownerDocument, event = doc.createEvent("MouseEvents");
	      event.initMouseEvent("click", true, true, doc.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
	      return node.dispatchEvent( event );
	    },

	    /*
		* Simulates a mousedown event on the given node.
		* @param {HTMLElement} node The DOM node to act on.
		* @member dreamunit		
		*/
	    mouseDown: function( node ){
	      //node = this.id( node );

	      if ( node.click ) {
	        return node.click();
	      }

	      var doc = node.ownerDocument, event = doc.createEvent("MouseEvents");
	      event.initMouseEvent("mousedown", true, true, doc.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
	      return node.dispatchEvent( event );
	    },

	    /*
		* Simulates a focus event on the given node.
		* @param {HTMLElement} node The DOM node to act on.
		* @member dreamunit		
		*/
	    focus: function( node ){
	      //node = this.id( node );

	      if ( node.focus ) {
	        return node.focus();
	      }

	      var doc = node.ownerDocument, event = doc.createEvent("UIEvents");
	      event.initUIEvent("DOMFocusIn", true, true, doc.defaultView, 1);
	      return node.dispatchEvent( event );
	    },

	    /*
		* Sets the value property of a node.
		* @member dreamunit		
		*/
	    value: function( node, text ){
	      //node = this.id( node );

	      node.value = text;
	    },

		/*
		* Simulates a keypress event on a given node with a given letter.
		* @param {HTMLElement} node The node to act on.
		* @param {String} letter A character to send to the node.
		* @member dreamunit		
		*/
		key: function( node, letter ){
			//node = this.id( node );

			var keyCode = letter, charCode = 0;

			if ( typeof keyCode == "string" ) {
				charCode = keyCode.charCodeAt(0);
				keyCode = 0;
			}

			var doc = node.ownerDocument, event = doc.createEvent("KeyEvents");
			event.initKeyEvent("keypress", true, true, doc.defaultView, false, false, false, false, keyCode, charCode);
			return node.dispatchEvent( event );
		},
	
		/*
		* Sniffs the status of an object and puts it into the log
		* @member dreamunit
		*/		
		inspect: function(object) {
			var res="";
			if (typeof object == "object") {
				for (item in object) {
					res += item.toString() + ":" + object[item].toString(); + ",";
				}
			} else {
				res = object.toString();
			}
			return res;
		},
	
	
		/*
		* Holds the log results
		* @member dreamunit
		* @private
		*/		
		logEntries: [],
		/*
		* The log module for reporting information
		* @member dreamunit
		*/		
		log: function(object) {
			this.logEntries[this.logEntries.length] = this.inspect(object);
		},
		
		
		
		/*
		* Holds the DIFF algorithm
		* @member dreamunit
		* @private
		*/
		diff: {
			/*
			 * Javascript Diff Algorithm
			 *  By John Resig (http://ejohn.org/)
			 *  Modified by Chu Alan "sprite"
			 *  Modified again by Alexei White (changed del and ins to s and i)
			 *
			 * Released under the MIT license.
			 *
			 * More Info:
			 *  http://ejohn.org/projects/javascript-diff-algorithm/
			 */

			escape: function(s) {
			    var n = s;
			    n = n.replace(/&/g, "&amp;");
			    n = n.replace(/</g, "&lt;");
			    n = n.replace(/>/g, "&gt;");
			    n = n.replace(/"/g, "&quot;");

			    return n;
			},

			diffString: function( o, n ) {
			  o = o.replace(/\s+$/, '');
			  n = n.replace(/\s+$/, '');

			  var out = dreamunit.diff.diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/) );
			  var str = "";

			  var oSpace = o.match(/\s+/g);
			  if (oSpace == null) {
			    oSpace = ["\n"];
			  } else {
			    oSpace.push("\n");
			  }
			  var nSpace = n.match(/\s+/g);
			  if (nSpace == null) {
			    nSpace = ["\n"];
			  } else {
			    nSpace.push("\n");
			  }

			  if (out.n.length == 0) {
			      for (var i = 0; i < out.o.length; i++) {
			        str += '<s>' + escape(out.o[i]) + oSpace[i] + "</s>";
			      }
			  } else {
			    if (out.n[0].text == null) {
			      for (n = 0; n < out.o.length && out.o[n].text == null; n++) {
			        str += '<s>' + escape(out.o[n]) + oSpace[n] + "</s>";
			      }
			    }

			    for ( var i = 0; i < out.n.length; i++ ) {
			      if (out.n[i].text == null) {
			        str += '<i>' + escape(out.n[i]) + nSpace[i] + "</i>";
			      } else {
			        var pre = "";

			        for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) {
			          pre += '<s>' + escape(out.o[n]) + oSpace[n] + "</s>";
			        }
			        str += " " + out.n[i].text + nSpace[i] + pre;
			      }
			    }
			  }

			  return str;
			},

			randomColor: function() {
			    return "rgb(" + (Math.random() * 100) + "%, " + 
			                    (Math.random() * 100) + "%, " + 
			                    (Math.random() * 100) + "%)";
			},
			
			diffString2: function( o, n ) {
			  o = o.replace(/\s+$/, '');
			  n = n.replace(/\s+$/, '');

			  var out = dreamunit.diff.diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/) );

			  var oSpace = o.match(/\s+/g);
			  if (oSpace == null) {
			    oSpace = ["\n"];
			  } else {
			    oSpace.push("\n");
			  }
			  var nSpace = n.match(/\s+/g);
			  if (nSpace == null) {
			    nSpace = ["\n"];
			  } else {
			    nSpace.push("\n");
			  }

			  var os = "";
			  var colors = new Array();
			  for (var i = 0; i < out.o.length; i++) {
			      colors[i] = randomColor();

			      if (out.o[i].text != null) {
			          os += '<span style="background-color: ' +colors[i]+ '">' + 
			                escape(out.o[i].text) + oSpace[i] + "</span>";
			      } else {
			          os += "<del>" + escape(out.o[i]) + oSpace[i] + "</del>";
			      }
			  }

			  var ns = "";
			  for (var i = 0; i < out.n.length; i++) {
			      if (out.n[i].text != null) {
			          ns += '<span style="background-color: ' +colors[out.n[i].row]+ '">' + 
			                escape(out.n[i].text) + nSpace[i] + "</span>";
			      } else {
			          ns += "<ins>" + escape(out.n[i]) + nSpace[i] + "</ins>";
			      }
			  }

			  return { o : os , n : ns };
			},

			diff: function( o, n ) {
			  var ns = new Object();
			  var os = new Object();

			  for ( var i = 0; i < n.length; i++ ) {
			    if ( ns[ n[i] ] == null )
			      ns[ n[i] ] = { rows: new Array(), o: null };
			    ns[ n[i] ].rows.push( i );
			  }

			  for ( var i = 0; i < o.length; i++ ) {
			    if ( os[ o[i] ] == null )
			      os[ o[i] ] = { rows: new Array(), n: null };
			    os[ o[i] ].rows.push( i );
			  }

			  for ( var i in ns ) {
			    if ( ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1 ) {
			      n[ ns[i].rows[0] ] = { text: n[ ns[i].rows[0] ], row: os[i].rows[0] };
			      o[ os[i].rows[0] ] = { text: o[ os[i].rows[0] ], row: ns[i].rows[0] };
			    }
			  }

			  for ( var i = 0; i < n.length - 1; i++ ) {
			    if ( n[i].text != null && n[i+1].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null && 
			         n[i+1] == o[ n[i].row + 1 ] ) {
			      n[i+1] = { text: n[i+1], row: n[i].row + 1 };
			      o[n[i].row+1] = { text: o[n[i].row+1], row: i + 1 };
			    }
			  }

			  for ( var i = n.length - 1; i > 0; i-- ) {
			    if ( n[i].text != null && n[i-1].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null && 
			         n[i-1] == o[ n[i].row - 1 ] ) {
			      n[i-1] = { text: n[i-1], row: n[i].row - 1 };
			      o[n[i].row-1] = { text: o[n[i].row-1], row: i - 1 };
			    }
			  }

			  return { o: o, n: n };
			}
		}
	}
	
}();
